package prolog.io

import scala.util.parsing.combinator._
import scala.collection.mutable._
import java.io.File
import java.io.BufferedReader
import java.io.FileReader
//import scala.Console
import prolog.terms._
import prolog.builtins.fail_
import prolog.builtins.true_

class TermParser() extends RegexParsers {

  var vars: LinkedHashMap[String, Var] = null

  def mkVar(x: String) = {
    val maybeV: Option[Var] = vars.get(x)
    maybeV match {
      case None => {
        val v = new Var()
        vars.put(x, v)
        v
      }
      case Some(a) => a
    }
  }

  val varToken: Parser[String] = """[A-Z_]\w*""".r
  val symToken: Parser[String] = """[a-z]\w*""".r
  val numToken: Parser[String] = """(-)?(\d+)(\.\d+)?""".r
  val specToken: Parser[String] = """[!.]|\\\+|\[\]""".r
  //val eocToken: Parser[String] = """.[\n \r\f\t]""".r

  def clause: Parser[List[Term]] = (head ~ opt(body) ~ opt(".")) ^^
    { case h ~ None ~ _ => List(h); case h ~ Some(bs) ~ _ => (h :: bs) }

  def head: Parser[Term] = term
  def body: Parser[List[Term]] = ":-" ~> repsep(term, ",")
  def term: Parser[Term] = listTerm | funTerm // this order is important for "[]"

  def funTerm: Parser[Term] =
    (idToken ~ opt(args)) ^^
      {
        case x ~ None => {
          val b = TermParser.string2ConstBuiltin(x)
          if (!b.eq(null)) b
          else new Const(x)
        }
        case x ~ Some(xs) => {
          val l = xs.length
          val array = new Array[Term](l)
          xs.copyToArray(array)
          if (l == 2 && x == ".")
            new Cons(xs(0), xs(1))
          else
            TermParser.toFunBuiltin(new Fun(x, array))
        }
      } |
      varToken ^^ { x => mkVar(x) } |
      numToken ^^
      { x => new Real(x) }

  def listTerm: Parser[Term] =
    listArgs ^^
      {
        case (xs ~ None) =>
          TermParser.list2cons(xs.asInstanceOf[List[Term]], Const.nil)
        case (xs ~ Some(x)) =>
          TermParser.list2cons(
            xs.asInstanceOf[List[Term]],
            x.asInstanceOf[Term])
      }

  def listArgs: Parser[Any] = "[" ~> (repsep(term, ",") ~ opt("|" ~> term)) <~ "]"

  def args: Parser[List[Term]] = "(" ~> repsep(term, ",") <~ ")"

  def idToken: Parser[String] = symToken | specToken

  def parseWithVars(text: String) = {
    vars = new LinkedHashMap[String, Var]()
    val termList = parseAll(clause, text) match {
      case Success(xss, _) => xss
      case other => {
        println("*** syntax error: " + other)
        Nil
      }
    }
    (termList, vars)
  }

  def parse(text: String): List[Term] = parseWithVars(text)._1

  def parse(texts: List[String]): List[List[Term]] = texts.map(parse)

  def file2clauses(fname: String): List[List[Term]] = {
    val file = new File(fname)
    val reader = new BufferedReader(new FileReader(file))

    var line = ""
    var content = new ArrayBuffer[List[Term]]()
    while (!line.eq(null)) {
      line = reader.readLine
      if (!line.eq(null)) {
        val clause =
          if ("" == line || line.startsWith("%")) Nil
          else parse(line)
        if (Nil != clause) content += clause
      }
    }

    reader.close
    content.toList

  }

  def readGoal() = {
    print("?- ")
    val s = readLine
    if (s.eq(null)) null
    else {
      parseWithVars("true:-" + s)
    }
  }

}

object TermParser {

  val builtinMap = new HashMap[String, Const]()
  builtinMap.put("true", true_())
  builtinMap.put("fail", fail_())

  private def string2builtin(s: String): Const = {

    val res = builtinMap.get(s) match {
      case None =>
        try {
          val b = Class.forName("prolog.builtins." + s).newInstance
          if (b.isInstanceOf[FunBuiltin] || b.isInstanceOf[ConstBuiltin]) {
            val c = b.asInstanceOf[Const]
            builtinMap.put(s, c)
            c
          } else
            null
        } catch {
          case _ => {
            //println("string2builtin: " + s + "=>" + "CATCH")
            null
          }
        }
      case Some(b) => b
    }
    //println("string2builtin: " + s + "=>" + res)
    res
  }

  def string2ConstBuiltin(s: String): ConstBuiltin =
    string2builtin(s: String) match {
      case null => null
      case b: ConstBuiltin => b
      case _ => null
    }

  def string2FunBuiltin(s: String): FunBuiltin =
    string2builtin(s: String) match {
      case null => null
      case b: FunBuiltin => b
      case _ => null
    }

  def toFunBuiltin(f: Fun): Fun = {
    //println("toFunBuiltin bef: " + f + "=>" + f.getClass)
    val proto: FunBuiltin = string2FunBuiltin(f.sym)

    val res =
      if (proto.eq(null)) f
      else if (f.args.length != proto.arity) f
      else {
        val b = proto.funClone
        b.args = f.args
        //println("clone=" + b)
        b
      }
    //println("toFunBuiltin aft: " + res + "=>" + res.getClass)
    res
  }
  //def toFunBuiltin(s: string,args:Array[Term]) = {}

  def printclause(xs: List[Term]) = {
    print(xs.head)
    val ys = xs.tail
    if (!ys.isEmpty) {
      print(":-")
      print(ys.head)
      ys.tail.foreach(x => print("," + x))
    }
    println("")
  }

  def special(s: String) = s match {
    case "+" => "plus"
    case "-" => "dif"
    case other => other
  }

  def list2cons(xs: List[Term], z: Term): Term = xs match {
    case Nil => z
    case y :: ys => new Cons(y, list2cons(ys, z))
  }
}